﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;

namespace comm_num_tp1
{
    
    public class GpsAcquireStream
    {
        #region private variables
        private volatile bool _startedToRead, _connected;
        private SerialPort _sPort = new SerialPort();
        private char[] _streamBuffer;
        private GpGSVList _gsvList;
        private string _GpStreamRawStream = "";
        #endregion

        public delegate void GpsEventHandler(object sender, GpsStreamEventArgs gpsE);
        public event GpsEventHandler GpGgaEvent;
        public event GpsEventHandler GpRmcEvent;
        public event GpsEventHandler GpGsvEvent;
        public event GpsEventHandler GpsNewStreamEvent;

        #region constructor
        public GpsAcquireStream()
        {

        }
        #endregion

        #region accessors
        public string comName
        {
            get
            {
                return _sPort.PortName;
            }
            set
            {
                if (!String.IsNullOrEmpty(value))
                    _sPort.PortName = value.ToUpper();
            }
        }
        public int baudRate
        {
            get
            {
                return _sPort.BaudRate;
            }
            set
            {
                _sPort.BaudRate = value;
            }
        }
        public Parity parity
        {
            get
            {
                return _sPort.Parity;
            }
            set
            {
                _sPort.Parity = value;
            }
        }
        public int dataBits
        {
            get
            {
                return _sPort.DataBits;
            }
            set
            {
                _sPort.DataBits = value;
            }
        }
        public StopBits stopBits
        {
            get
            {
                return _sPort.StopBits;
            }
            set
            {
                _sPort.StopBits = value;
            }
        }
        #endregion

        #region public methods
        /// <summary>
        /// Cette méthode sert a établir la connexion avec le port série
        /// </summary>
        /// <returns>true if successful, otherwise false</returns>
        public bool GpsConnect()
        {
            bool success = true;

            try
            {
                if (!_sPort.IsOpen)
                {
                    _sPort.Open();
                    _connected = true;
                }
                else
                {
                    success = false;
                    System.Windows.Forms.MessageBox.Show("Already connected !");
                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("Connexion a échoué : " + ex.Message, "Malfunction");
                success = false;
            }
            return success;
        }

        /// <summary>
        /// Disconnect from serial port
        /// </summary>
        /// <returns>true if operation successful, otherwise false</returns>
        public bool GpsDisconnect()
        {
            bool success = true;
            if (_connected)
            {
                if (_startedToRead)
                    GpsStreamStop();
                try
                {
                    if (_sPort.IsOpen)
                        _sPort.Close();
                    _connected = false;
                }
                catch (Exception ex)
                {
                    System.Windows.Forms.MessageBox.Show("Can't disconnect : " + ex.Message, "Malfunction");
                    success = false;
                }
            }

            return success;
        }


        public void GpsStreamStart()
        {
            _startedToRead = true;
            _sPort.DiscardInBuffer();
            // read in serial
            while (_startedToRead)
            {
                int value;
                try
                {
                    value = _sPort.ReadChar();
                    GpsBuildStream((char)value);
                }
                catch (TimeoutException ex)
                {
                    // nothing, Gps device is just not shooting information in our direction now
                }
                catch (InvalidOperationException ex)
                {
                    System.Windows.Forms.MessageBox.Show("Failed to read serial port : " + ex.Message);
                }
                catch (Exception ex)
                {
                    // System.Windows.Forms.MessageBox.Show("General Exception occurs : " + ex.Message);
                }

            }
        }

        /// <summary>
        /// Stop reading from serial port
        /// </summary>
        /// <returns>true if operation successful, otherwise false</returns>
        public bool GpsStreamStop()
        {
            bool success = true;

            if (_connected && _startedToRead)
            {
                _startedToRead = false;
                if (_sPort.IsOpen)
                {
                    _sPort.Close();
                    _connected = false;
                }
            }

            return success;
        }

        /// <summary>
        /// public method that launch the protectd OnGpggaEvent
        /// </summary>
        /// <param name="gpsE"></param>
        public void LauchGpGgaEvent(GpsStreamEventArgs gpsE)
        {
            OnGpGgaEvent(this, gpsE);
        }

        /// <summary>
        /// public method that launch the protectd OnGpRmcEvent
        /// </summary>
        /// <param name="gpsE"></param>
        public void LaunchGpRmcEvent(GpsStreamEventArgs gpsE)
        {
            OnGpRmcEvent(this, gpsE);
        }

        /// <summary>
        /// public method that launch the protectd OnGpGsvEvent
        /// </summary>
        /// <param name="gpsE"></param>
        public void LaunchGpGsvEvent(GpsStreamEventArgs gpsE)
        {
            OnGpGsvEvent(this, gpsE);
        }

        /// <summary>
        /// public method that launch the protectd OnGpsNewStreamEvent
        /// </summary>
        /// <param name="gpsE"></param>
        public void LaunchGpsNewStreamEvent(GpsStreamEventArgs gpsE)
        {
            OnGpsNewStreamEvent(this, gpsE);
        }
        #endregion

        #region private methods
        /// <summary>
        /// Build Gps Stream from incoming data
        /// Send it to the Gps hub that will determine which Gps stream is.
        /// </summary>
        /// <param name="value">A char received from GPS device</param>
        private void GpsBuildStream(char value)
        {
            if (_streamBuffer == null && value == '$')
            {
                _streamBuffer = new char[1];
                _streamBuffer[0] = value;
            }
            else if (_streamBuffer != null && value != '\n')
            {
                char[] stringbuffer = _streamBuffer;
                _streamBuffer = new char[stringbuffer.Length + 1];
                for (int i = 0; i < stringbuffer.Length; i++)
                {
                    _streamBuffer[i] = stringbuffer[i];
                }
                _streamBuffer[stringbuffer.Length] = value;
            }
            else if (_streamBuffer != null && value == '\n')
            {
                if (_streamBuffer.Length > 10 &&
                    _streamBuffer[_streamBuffer.Length - 1] == '\r')
                {
                    GpsStreamHub(_streamBuffer);
                }
                else
                {
                    char[] stringbuffer = _streamBuffer;
                    _streamBuffer = new char[stringbuffer.Length + 1];
                    for (int i = 0; i < stringbuffer.Length; i++)
                    {
                        _streamBuffer[i] = stringbuffer[i];
                    }
                    _streamBuffer[stringbuffer.Length] = value;
                }
            }
        }

        /// <summary>
        /// Find which Gps Stream is and fired the appropriate event
        /// </summary>
        /// <param name="sBuf">an array of char containing a Gps Data Stream</param>
        private void GpsStreamHub(char[] sBuf)
        {
            _streamBuffer = null;
            string preGpsStream = "";
            for (int i = 0; i < sBuf.Length; i++)
            {
                preGpsStream += sBuf[i].ToString();
            }

            _GpStreamRawStream = preGpsStream;
            string[] GpsStreamSplitted = preGpsStream.Split(',');
            switch (GpsStreamSplitted[0])
            {
                case "$GPGSV":
                    // build GpGsv object and fire event
                    GpGSVList newGpGSVList = GpsBuildGpGsv(GpsStreamSplitted);
                    if (newGpGSVList != null)
                        LaunchGpGsvEvent(new GpsStreamEventArgs(newGpGSVList));
                    break;
                case "$GPGGA":
                    // build GpGga object and fire event
                    GpGGA newGpGGA = GpsBuildGpGga(GpsStreamSplitted);
                    LauchGpGgaEvent(new GpsStreamEventArgs(newGpGGA));
                    
                    break;
                case "$GPRMC":
                    // build GpRmc object and fire event
                    GpRMC newGpRMC = GpsBuildGpRmc(GpsStreamSplitted);
                    LaunchGpRmcEvent(new GpsStreamEventArgs(newGpRMC));
                    break;
            }
        }

        private GpRMC GpsBuildGpRmc(string[] GpsStreamSplitted)
        {
            int arrayIndex = 0;
            string[] tempArray = new string[GpsStreamSplitted.Length];
            for (int i = 0; i < tempArray.Length; i++)
            {
                if (arrayIndex > 1 && GpsStreamSplitted[i] == "N" || GpsStreamSplitted[i] == "S" ||
                    GpsStreamSplitted[i] == "W" || GpsStreamSplitted[i] == "E" || GpsStreamSplitted[i] == "M")
                {
                    tempArray[arrayIndex - 1] += ',' + GpsStreamSplitted[i];
                }
                else
                {
                    tempArray[arrayIndex] = GpsStreamSplitted[i];
                    arrayIndex++;
                }
            }
            GpsStreamSplitted = tempArray;
            string[] temp2Array = GpsStreamSplitted[arrayIndex - 1].Split('*');
            if (temp2Array[0] == "N" || temp2Array[0] == "S" ||
                temp2Array[0] == "W" || temp2Array[0] == "E")
            {
                GpsStreamSplitted[arrayIndex - 2] += ',' + temp2Array[0];
                GpsStreamSplitted[arrayIndex - 1] = temp2Array[1];
            }
            else
            {
                GpsStreamSplitted[arrayIndex - 1] = temp2Array[0];
                GpsStreamSplitted[arrayIndex] = temp2Array[1];
            }

            GpRMC rmcStream = new GpRMC(GpsStreamSplitted[(int)GpRMCStreamItem.StreamIden],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamTime],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamWarning],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamLat],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamLon],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamSpeed],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamBearing],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamTimeStamp],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamDeclMagnetDeg],
                GpsStreamSplitted[(int)GpRMCStreamItem.StreamChecksum]);

            rmcStream.StreamRawStream = _GpStreamRawStream;
            return rmcStream;
        }

        private GpGGA GpsBuildGpGga(string[] GpsStreamSplitted)
        {
            int arrayIndex = 0;
            string[] tempArray = new string[GpsStreamSplitted.Length];
            for (int i = 0; i < tempArray.Length; i++)
            {
                if (arrayIndex > 1 && GpsStreamSplitted[i] == "N" || GpsStreamSplitted[i] == "S" ||
                    GpsStreamSplitted[i] == "W" || GpsStreamSplitted[i] == "E" || GpsStreamSplitted[i] == "M")
                {
                    tempArray[arrayIndex - 1] += ',' + GpsStreamSplitted[i];
                }
                else
                {
                    tempArray[arrayIndex] = GpsStreamSplitted[i];
                    arrayIndex++;
                }
            }
            GpsStreamSplitted = tempArray;
            string [] temp2Array = GpsStreamSplitted[arrayIndex - 1].Split('*');
            GpsStreamSplitted[arrayIndex - 1] = temp2Array[0];
            GpsStreamSplitted[arrayIndex] = temp2Array[1];

            GpGGA ggaStream = new GpGGA(GpsStreamSplitted[(int)GpGGAStreamItem.StreamIden],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamTime],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamLat],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamLon],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamFix],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamSatQty],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamDop],
                GpsStreamSplitted[(int)GpGGAStreamItem.StramAltitude],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamMsl],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamTimeStamp],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamDgpsStation],
                GpsStreamSplitted[(int)GpGGAStreamItem.StreamChecksum]);
            ggaStream.StreamRawStream = _GpStreamRawStream;

            return ggaStream;
        }

        private GpGSVList GpsBuildGpGsv(string[] GpsStreamSplitted)
        {
            GpGSVList newGpGSVList = null;
            GpGSV gsvStream = new GpGSV();
            gsvStream.StreamRawStream = _GpStreamRawStream;

            // is list empty and first GsvStream
            if (_gsvList == null && GpsStreamSplitted[2] == "1")
            {
                gsvStream = GsvStreamAddSat(gsvStream, GpsStreamSplitted);
                gsvStream.StreamIden = GpsStreamSplitted[0];
                gsvStream.StreamCompletStream = GpsStreamSplitted[1];
                gsvStream.StreamNo = GpsStreamSplitted[2];
                gsvStream.StreamVisibleSat = GpsStreamSplitted[3];
                _gsvList = new GpGSVList();
                _gsvList.AddSateliteSet(gsvStream);
            } // A second GsvStream
            else if (_gsvList != null && GpsStreamSplitted[2] == "2")
            {
                gsvStream = GsvStreamAddSat(gsvStream, GpsStreamSplitted);
                gsvStream.StreamIden = GpsStreamSplitted[0];
                gsvStream.StreamCompletStream = GpsStreamSplitted[1];
                gsvStream.StreamNo = GpsStreamSplitted[2];
                gsvStream.StreamVisibleSat = GpsStreamSplitted[3];
                _gsvList.AddSateliteSet(gsvStream);

                // is Only 2 GsvStream ?
                if (GpsStreamSplitted[1] == "2")
                {
                    newGpGSVList = _gsvList;
                    _gsvList = null;
                }
            }// a third and last GsvStream, if any
            else if (_gsvList != null && GpsStreamSplitted[2] == "3")
            {
                gsvStream = GsvStreamAddSat(gsvStream, GpsStreamSplitted);
                gsvStream.StreamIden = GpsStreamSplitted[0];
                gsvStream.StreamCompletStream = GpsStreamSplitted[1];
                gsvStream.StreamNo = GpsStreamSplitted[2];
                gsvStream.StreamVisibleSat = GpsStreamSplitted[3];
                _gsvList.AddSateliteSet(gsvStream);
                newGpGSVList = _gsvList;
                _gsvList = null;
            }
            return newGpGSVList;
        }

        private GpGSV GsvStreamAddSat(GpGSV gsvStream, string[] GpsStreamSplitted)
        {
            GpGSV tempGsv = new GpGSV();
            SateliteInfo singleSat;
            int satQty = 0;
            int myArrayIndex = 0;
            // Remove header info
            string[] tempSplitted = new string[GpsStreamSplitted.Length - 4];
            for (int i = 4; i < GpsStreamSplitted.Length; i++)
            {
                tempSplitted[myArrayIndex] = GpsStreamSplitted[i];
                myArrayIndex++;
            }

            satQty = Math.Abs(tempSplitted.Length / 4);
            for (int i = 0; i < satQty; i++)
            {
                string[] stringSat = SplitSateliteString(tempSplitted, i);
                singleSat = new SateliteInfo(stringSat[0], stringSat[1], stringSat[2], stringSat[3]);
                tempGsv.SateliteList.AddSatelite(singleSat);
            }
            return tempGsv;
        }

        private string[] SplitSateliteString(string[] satArray, int index)
        {
            string[] newSatArray = new string[4];
            int myindex = 0;
            for (int i = index * 4; myindex < 4; i++)
            {
                newSatArray[myindex] = satArray[i];
                myindex++;
            }

            // remove checksum if any
            newSatArray[3] = newSatArray[3].Split('*')[0];
            return newSatArray;
        }
        #endregion

        #region protected methods
        protected virtual void OnGpsNewStreamEvent(object sender, GpsStreamEventArgs gpsE)
        {
            if (GpsNewStreamEvent != null)
                GpsNewStreamEvent(sender, gpsE);
        }

        /// <summary>
        /// Launch event when a GGA Stream is complete in order to send to GUI
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="gpsE"></param>
        protected virtual void OnGpGgaEvent(object sender, GpsStreamEventArgs gpsE)
        {
            if (GpGgaEvent != null)
                GpGgaEvent(sender, gpsE);
        }

        /// <summary>
        /// Launch event when a RMC Stream is complete in order to send to GUI
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="gpsE"></param>
        protected virtual void OnGpRmcEvent(object sender, GpsStreamEventArgs gpsE)
        {
            if (GpRmcEvent != null)
                GpRmcEvent(sender, gpsE);
        }

        /// <summary>
        /// Launch event when a GSV Stream is complete in order to send to GUI
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="gpsE"></param>
        protected virtual void OnGpGsvEvent(object sender, GpsStreamEventArgs gpsE)
        {
            if (GpGsvEvent != null)
                GpGsvEvent(sender, gpsE);
        }
        #endregion
    }
}
